import os,tempfile,uuid,shutil,commands
import socket
import base64
import time
import logging
log = logging.getLogger(__name__)

from crv.model.crvmodel import *
from crv.model.rp import *

from sqlalchemy.orm import mapper

from crv.tools.puppettools import *

import pickle

class Puppet_ProfileMaker(ProfileMaker):

    def __new__(cls, *args, **kwds):
#        print "Puppet_ProfileMaker.__new__", args, kwds
        return ProfileMaker.__new__(cls, *args, **kwds)
    
    def __init__(self,name,doc="",ssldir="/opt/cloudcrv/varpuppet/ssl",confdir="/opt/cloudcrv/puppet", puppetca="/usr/sbin/puppetca", vardir="/opt/cloudcrv/varpuppet"):
        super(Puppet_ProfileMaker, self).__init__("puppet")
        #Map of "profileuuid->pathToTarredBase64file
        self.setAttr("confdir",confdir)
        self.setAttr("nodesfile","%s/manifests/nodes.pp"%confdir)
        self.setAttr("puppetca",puppetca)
        self.setAttr("ssldir",ssldir)
        self.setAttr("vardir",vardir)        
        self.setAttr("name",name)
        self.setAttr("doc",doc)
        
    def debug(self,str):
        log.debug(str)
        
    def provideProfile(self,role,doReverse=False):
#        if role.vm.getAttr("MetaDataProvided")!="True":
#            self.provideMetaData(role)

        #possible multiple roles per VM
        pp=self.readFromFile()
        if pp==None:
            pp=Puppet_ProfileMaker.PP()

        #locate the node
        thisnode=None
        for inode in pp.nodes:
            if str(inode.vmid)==str(role.vm.VM_ID):
                thisnode=inode
                break
        if thisnode==None:
            thisnode=Puppet_ProfileMaker.NODE()
            thisnode.vmid=role.vm.VM_ID
            thisnode.addr="https://cloudcrvsrv:8080"
#            thisnode.addr="https://%s:8080"%socket.gethostbyname(socket.gethostname())
            thisnode.hostname=role.vm.getAttr("certname")
            pp.addNode(thisnode)

        #locate the role in the node
        thisrole=None
        for irole in thisnode.roles:
            if str(irole.roleid)==str(role.Role_ID):
                thisrole=irole
                break
        if thisrole==None:
            thisrole=Puppet_ProfileMaker.ROLE()
            thisrole.roleid=role.Role_ID

            #now store the local dependencies to the ROLE instance, see below for enforcing execution order of locally dependent roles
            #assuming any receiver is always added later than all its providers.
            for ir in role.dependOnLocal:
                thisrole.dependOnLocal.append(ir.Role_ID)

            thisnode.addRole(thisrole)

            
        if doReverse:
            thisrole.profile=role.roleDef.getAttr("profile")+"_reverse"
        else:
            thisrole.profile=role.roleDef.getAttr("profile")

        self.saveToFile(pp)

        #Provide the nodes.pp for compiled catalog
        #only one role in the vm
        app=Puppet_ProfileMaker.PP()
        anode=Puppet_ProfileMaker.NODE()
        anode.vmid=role.vm.VM_ID
        anode.addr="https://cloudcrvsrv:8080"
        anode.hostname=role.vm.getAttr("certname")
        app.addNode(anode)

        arole=Puppet_ProfileMaker.ROLE()
        arole.roleid=role.Role_ID
        anode.addRole(arole)

        if doReverse:
            arole.profile=role.roleDef.getAttr("profile")+"_reverse"
        else:
            arole.profile=role.roleDef.getAttr("profile")

        log.info(str(anode))
        role.setAttr("sitepp",str(anode))

        sitepp=str(anode)
        confdir=role.cluster[0].getAttr("confdir")
        nodename=role.vm.getAttr("certname")
        facts=base64.b64decode(role.vm.getAttr("facts"))

        log.debug("ready to generate catalog")
        log.debug(sitepp)
        log.debug(confdir)
        log.debug(nodename)
        log.debug(facts)

        catalogtext=compileCatalog(confdir,sitepp,nodename,facts)
        log.debug(catalogtext)
        
        if catalogtext is None:
            role.setAttr("puppetcatalog",None)
            log.debug("ERRORERROR can't generate catalog for role %s"%str(role))
    
        role.setAttr("puppetcatalog",catalogtext)
        log.debug(str(role.vm.getAttr("puppetcatalog")))
        role.setAttr("DoUpdate","YES")

    def provideReverseProfile(self,role):
        self.provideProfile(role,doReverse=True)

    def readPPFromStr(self,strarray,pos):
        pp=None
        
        #first look for begin part
        cur=pos[0]
        while cur<len(strarray):
            spline=strarray[cur].split()
            if len(spline)>0 and spline[0].find("###")>=0:
                if spline[0]=="###BEGINPP":
                    pp=Puppet_ProfileMaker.PP()
                    break
                else:
                    return None
            cur=cur+1

        
        if cur>=len(strarray):
            return None

        pos[0]=cur+1
        node=None
        while True:
            node=self.readNODEFromStr(strarray,pos)
            if node==None:
                break
            else:
                pp.addNode(node)
                
        cur=pos[0]
        while cur<len(strarray):
            spline=strarray[cur].split()
            if len(spline)>0 and spline[0].find("###")>=0:
                if spline[0]=="###ENDPP":
                    return pp
                else:
                    return None
            cur=cur+1
            
        return None
            
    def readNODEFromStr(self,strarray,pos):
        node=None
        
        #first look for begin part
        cur=pos[0]
        while cur<len(strarray):
            spline=strarray[cur].split()
            if len(spline)>0 and spline[0].find("###")>=0:
                if spline[0]=="###BEGINNODE":
                    node=Puppet_ProfileMaker.NODE()
                    node.vmid=spline[1]
                    node.hostname=spline[2]
                    node.addr=spline[3]
                    break
                else:
                    return None
            cur=cur+1

        if cur>=len(strarray):
            return None

        pos[0]=cur+1
        role=None
        while True:
            role=self.readROLEFromStr(strarray,pos)
            if role==None:
                break
            else:
                node.addRole(role)
                
        cur=pos[0]
        while cur<len(strarray):
            spline=strarray[cur].split()
            if len(spline)>0 and spline[0].find("###")>=0:
                if spline[0]=="###ENDNODE":
                    pos[0]=cur+1
                    return node
                else:
                    return None
            cur=cur+1
            
        return None
            
    def readROLEFromStr(self,strarray,pos):
        role=None
        
        #first look for begin part
        cur=pos[0]
        while cur<len(strarray):
            spline=strarray[cur].split()
            if len(spline)>0 and spline[0].find("###")>=0:
                if spline[0]=="###BEGINROLE":
                    role=Puppet_ProfileMaker.ROLE()
                    role.roleid=spline[1]
                    role.profile=spline[2]
                    break
                else:
                    return None
            cur=cur+1

        if cur>=len(strarray):
            return None

        pos[0]=cur+1

        cur=pos[0]
        while cur<len(strarray):
            spline=strarray[cur].split()
            if len(spline)>0 and spline[0].find("###")>=0:
                if spline[0]=="###ENDROLE":
                    pos[0]=cur+1
                    return role
                else:
                    return None
            cur=cur+1
            
        return None
            
    class PP:
        def addNode(self,node):
            self.nodes.append(node)
            node.pp=self
            
        def __init__(self):
            self.nodes=[]
            self.prescript=""
            self.postscript=""
            self.__template="""
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# This file is managed by CloudCRV.
# DO NOT EDIT this files except for the two editable blocks
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #


#$#def getPP():
#$#    pp=Puppet_ProfileMaker.PP()
#$#    pp.prescript=\"\"\"%(prescript)s#$#\"\"\"
%(nodestr)s
#$#    pp.postscript=\"\"\"%(postscript)s#$#\"\"\"
#$#    return pp
"""
        def __str__(self):
            nodestr=""
            for i in self.nodes:
                nodestr=nodestr+str(i)
            return self.__template%{'nodestr':nodestr, 'prescript':self.prescript, 'postscript':self.postscript}

    class NODE:
        def addRole(self,role):
            self.roles.append(role)
            role.node=self

        def getRoleByID(self,roleID):
            for i in self.roles:
                if str(i.roleid)==str(roleID):
                    return i
            return None
        
        def __init__(self,vmid="",hostname="",addr=""):
            self.roles=[]
            self.pp=None
            self.vmid=vmid
            self.hostname=hostname
            self.addr=addr
            self.__template="""
  $AutoMode=true
  $CloudCRV=true
#$#    node=Puppet_ProfileMaker.NODE()
#$#    node.vmid=\"%(vmid)s\"
#$#    node.hostname=\"%(hostname)s\"
#$#    node.addr=\"%(addr)s\"

%(classdefs)s

node '%(hostname)s' {
 $webaddr=\"%(addr)s\"
 $vmid=\"%(vmid)s\"
 %(rolestr)s
}
#$#    pp.addNode(node)
    """
        # use classes to enforce execution order for locally dependent roles.
        
        def __str__(self):
            classdefs=""
            rolestr=""
            for i in self.roles:
                rolestr=rolestr+str(i)
                classdefs=classdefs+i.getClassDefs()

#            import pdb; pdb.set_trace()
            return self.__template%{'vmid':self.vmid,
                                    'hostname':self.hostname,
                                    'addr':self.addr,
                                    'rolestr':rolestr,
                                    'classdefs':classdefs}

    class ROLE:
        def __init__(self,roleid="",profile=""):
            self.node=None
            self.roleid=roleid
            self.profile=profile
            self.dependOnLocal=[]
            
            self.__template="""
#$#    role=Puppet_ProfileMaker.ROLE()
#$#    role.roleid=\"%(roleid)s\"
#$#    role.profile=\"%(profile)s\"
#$#    role.dependOnLocal=%(dependonlocal)s

 include cls_%(profile)s_%(roleid)s

#$#    node.addRole(role)
"""
            self.__classdefs_template="""
class cls_%(profile)s_%(roleid)s {
 %(profile)s { %(profile)s_%(roleid)s :
  webaddr => \"%(addr)s\",
  roleid => \"%(roleid)s\", %(require)s
 }
}
                """
            self.__classdefs_require_template="  \"cls_%(profile)s_%(roleid)s\" "

        def getClassDefs(self):
            requirestr=""
            if len(self.dependOnLocal)>0:
                requirestr="require => Class[%(requiredclasses)s]," % {"requiredclasses":",".join( map( lambda x: self.__classdefs_require_template % { "profile":self.node.getRoleByID(x).profile, "roleid":self.node.getRoleByID(x).roleid}, self.dependOnLocal) ) }

            return self.__classdefs_template%{'profile':self.profile,
                                              'roleid':self.roleid,
                                              'require':requirestr,
                                              'addr':self.node.addr}
                
        def __str__(self):
            return self.__template%{'roleid':self.roleid,
                                    'profile':self.profile,
                                    'dependonlocal':"["+",".join(map(lambda x: str(x), self.dependOnLocal))+"]"}
        

    def readFromFile(self):
        inf=open(self.getAttr("nodesfile"),"r")
        strarray=inf.readlines()
        inf.close()

        outstr=""
        inquote=False
        for iline in strarray:
#            print iline, inquote
            if iline.find("#$#")==0: #this line is python code
                outstr=outstr+iline[3:]
                if iline.find("\"\"\"")>=0:
                    inquote=not inquote
            else: #this line is puppet code
                if inquote:
                    outstr=outstr+iline.replace("\"","\\\"").replace("\'","\\\'")
                else:
                    outstr=outstr+"#$#"+iline

        self.debug(outstr)

        exec(outstr)
        return getPP()

    def saveToFile(self,pp):
        ouf=open(self.getAttr("nodesfile"),"w")
        ouf.write(str(pp))
        ouf.close()
        
    def provideMetaData(self,role):
        #for now, run puppetca to generate keys, and tar them up, set the base64 encoded value to MetaData field of the VM's attr
        self.debug("Puppet_ProfileMaker::provideMetaData::called. RoleID=%s, VMID=%s"%(role.Role_ID,role.vm.VM_ID))

        vmip=role.vm.getAttr("PrivateIPV4")

        confdir=self.getAttr("confdir")
        ssldir=self.getAttr("ssldir")
        vardir=self.getAttr("vardir")        
        
        if vmip==None:
            raise RuntimeError("Can't get PrivateIPV4 for vm name=%s id=%s, role name=%s id=%s"%(role.vm.getAttr("name"),role.vm.VM_ID,role.getAttr("name"),role.Role_ID))
#            role.currentSC=Role.SC.SC_EXCEPTION
#            role.vm.currentSC=VM.SC.SC_EXCEPTION            

        self.debug("vmip=%s"%vmip)
        
        #run puppetca
        cmdstr="%s --confdir=%s --ssldir=%s --vardir=%s --generate %s"%(self.getAttr("puppetca"),confdir, ssldir, vardir, vmip)
                                   
        (status,output)=commands.getstatusoutput(cmdstr)
        if status!=0:
            raise RuntimeError("Can't run cmdstr=%s, statuscode=%s, output=%s"%(cmdstr,status,output))

        #locate the files
        #$ssldir/ssl/ca/signed/172.19.2.3.pem
        #$ssldir/ssl/ca/ca_crt.pem
        #$ssldir/ssl/private_keys/172.19.2.3.pem
        #copy them to a tmp folder and put into (with ssl as the base dir)
        #$ssldir/ssl/certs/172.19.2.3.pem
        #$ssldir/ssl/certs/ca.pem
        #$ssldir/ssl/private_keys/172.19.2.3.pem

        myuuid=str(uuid.uuid4())
        workdir=tempfile.gettempdir()+"/"+myuuid
        targetssldir=workdir+"/ssl"
        
        self.debug("myuuid=%s, workdir=%s, ssldir=%s"%(myuuid,workdir,ssldir))
        
        os.makedirs(targetssldir,0700)
        os.makedirs(targetssldir+"/certs",0700)
        os.makedirs(targetssldir+"/private_keys",0700)
        
        shutil.copyfile("%s/ca/signed/%s.pem"%(ssldir,vmip),"%s/certs/%s.pem"%(targetssldir,vmip))
        shutil.copyfile("%s/ca/ca_crt.pem"%ssldir,"%s/certs/ca.pem"%targetssldir)
        shutil.copyfile("%s/private_keys/%s.pem"%(ssldir,vmip),"%s/private_keys/%s.pem"%(targetssldir,vmip))

        olddir=os.getcwd()
        os.chdir(workdir)
        cmdstr="tar zc ssl | base64 -w 0 "
        (status,output)=commands.getstatusoutput(cmdstr)
        if status!=0:
            raise RuntimeError("Can't run cmdstr=%s, statuscode=%s, output=%s"%(cmdstr,status,output))
        os.chdir(olddir)

        role.vm.setAttr("MetaData",output)
        role.vm.setAttr("MetaDataProvided","True")
        
